﻿using System.ComponentModel;
using System.Diagnostics;
using NewLife;
using NewLife.Log;
using NewLife.Security;

namespace XCode.Common;

/// <summary>数据模拟</summary>
/// <typeparam name="T"></typeparam>
public class DataSimulation<T> : DataSimulation where T : Entity<T>, new()
{
    /// <summary>实例化</summary>
    public DataSimulation() => Factory = Entity<T>.Meta.Factory;
}

/// <summary>数据模拟</summary>
public class DataSimulation
{
    #region 属性
    /// <summary>实体工厂</summary>
    public IEntityFactory Factory { get; set; } = null!;

    /// <summary>事务提交的批大小</summary>
    public Int32 BatchSize { get; set; } = 1000;

    /// <summary>并发线程数</summary>
    public Int32 Threads { get; set; } = 1;

    /// <summary>直接执行SQL</summary>
    public Boolean UseSql { get; set; }

    /// <summary>分数</summary>
    public Int32 Score { get; private set; }
    #endregion

    #region 构造
    /// <summary>实例化</summary>
    public DataSimulation()
    {
        //Threads = Environment.ProcessorCount * 3 / 4;
        //if (Threads < 1) Threads = 1;
    }
    #endregion

    #region 方法
    /// <summary>开始执行</summary>
    /// <param name="count"></param>
    public void Run(Int32 count)
    {
        var set = XCode.XCodeSetting.Current;
        set.TraceSQLTime = 0;

        var fact = Factory;
        var session = fact.Session;
        //var pst = XCodeService.Container.ResolveInstance<IEntityPersistence>();
        var pst = fact.Persistence;
        var conn = fact.ConnName;

        // 关闭SQL日志
        //XCode.Setting.Current.ShowSQL = false;
        session.Dal.Db.ShowSQL = false;
        // 不必获取自增返回值
        fact.AutoIdentity = false;

        Console.WriteLine();
        WriteLog("数据模拟 Count={0:n0} Threads={1} BatchSize={2} UseSql={3}", count, Threads, BatchSize, UseSql);

        // 预热数据表
        WriteLog("{0} 已有数据：{1:n0}", fact.TableName, session.Count);

        // 准备数据
        var list = new List<IEntity>();
        var qs = new List<String>();

        var cpu = Environment.ProcessorCount;
        //WriteLog("正在准备数据[CPU={0}]：", cpu);
        var sw = Stopwatch.StartNew();
        Parallel.For(0, cpu, n =>
        {
            WriteLog("正在准备数据[CPU={0}]：", n);
            //fact.ConnName = conn + n;

            var k = 0;
            for (var i = n; i < count; i += cpu, k++)
            {
                if (k % BatchSize == 0) Console.Write(".");

                var e = fact.Create();
                foreach (var item in fact.Fields)
                {
                    if (item.IsIdentity) continue;

                    if (item.Type == typeof(Int32))
                        e.SetItem(item.Name, Rand.Next());
                    else if (item.Type == typeof(String))
                        e.SetItem(item.Name, Rand.NextString(8));
                    else if (item.Type == typeof(DateTime))
                        e.SetItem(item.Name, DateTime.Now.AddSeconds(Rand.Next(-10000, 10000)));
                }
                var sql = "";
                if (UseSql) sql = pst.GetSql(session, e, DataObjectMethodType.Insert);
                lock (list)
                {
                    list.Add(e);
                    if (UseSql && !sql.IsNullOrEmpty()) qs.Add(sql);
                }
            }
        });
        sw.Stop();
        Console.WriteLine();
        var ms = sw.Elapsed.TotalMilliseconds;
        WriteLog("耗时：{0:n0}ms / {1:n0}", ms, list.Count * 1000L / ms);

        sw.Restart();

        Console.WriteLine();
        var ths = Threads;
        //WriteLog("正在准备写入[Threads={0}]：", ths);
        Parallel.For(0, ths, n =>
        {
            WriteLog("正在准备写入[Thread={0}]：", n);
            //fact.ConnName = conn + n;

            var k = 0;
            EntityTransaction? tr = null;
            var dal = session.Dal;
            for (var i = n; i < list.Count; i += ths, k++)
            {
                if (k % BatchSize == 0)
                {
                    Console.Write(".");
                    tr?.Commit();

                    tr = session.CreateTrans();
                }

                if (!UseSql)
                    list[i].Insert();
                else
                    dal.Execute(qs[i]);
            }
            tr?.Commit();
        });

        sw.Stop();
        Console.WriteLine();
        WriteLog("数据写入完毕！");
        ms = sw.Elapsed.TotalMilliseconds;
        var speed = list.Count * 1000L / ms;
        WriteLog("{2}插入{3:n0}行数据，耗时：{0:n0}ms 速度：{1:n0}tps", ms, speed, session.Dal.DbType, list.Count);

        Score = (Int32)speed;

        session.ClearCache("SqlInsert", false);
        var t = session.Count;
        Thread.Sleep(100);
        WriteLog("{0} 共有数据：{1:n0}", fact.TableName, session.Count);
    }
    #endregion

    #region 日志
    /// <summary>日志</summary>
    public ILog Log { get; set; } = Logger.Null;

    /// <summary>写日志</summary>
    /// <param name="format"></param>
    /// <param name="args"></param>
    public void WriteLog(String format, params Object?[] args)
    {
        Log?.Info(format, args);
    }
    #endregion
}